import core from '@/core/'

// 记录至少间隔多长时间内的请求会被做去重处理。单位毫秒。实际记录的时间是整个响应过程的时间加上这个设置时间
const recurdelay = 300
/**
 * 记录的请求
 * key是记录的md5，value是
 * 上次请求时间timestamp、请求动作action和请求结果：res、error
 * 
 * 有些被拦截的请求要移除出数组
 */
export const RequestActions = {}
// 记录请求缓存的key值。该值在启动初始化时也会被赋值
export const requestMd5s = []
// 删除缓存
export const deleteCache = (md5) => {
	if (md5) {
		core.storage.removeData(md5)
		const index = requestMd5s.indexOf(md5)
		if (index > -1) {
			requestMd5s.splice(index, 1)
		}
	}
}

/**
 * 完全相同的请求，至少在返回结果之前不能重复，或者设置最少间隔：recurdelay。
 * 方案：
 * 1，请求的同时，就记录当前请求
 * 2，下次请求时，优先从记录的请求中返回记录的动作
 * 3，请求完毕，需执行记录的请求总返回的动作
 */
const setRequest = ({res, error} = {}, md5) => {
	if (RequestActions[md5]) {
		RequestActions[md5].action && RequestActions[md5].action({res, error})
		// 有响应结果后再删除
		core.delayAction(() => {
			delete RequestActions[md5]
		}, recurdelay)
	}
}

/**
 * 将请求信息缓存到本地
 * 并将其key值记录下来并记录，方便再次启动时清空
 * 不仅要清空记录的key值，同时也将清空key值对应的信息
 * 在对应的缓存时间后，将自动清理
 */
const setData = (res, md5, cache, cacheFix) => {
	if (res) {
		res.CacheMd5 = md5
		var jsString = JSON.stringify(res)
		// 在存储的信息中插入时间戳，没有改变原来的数据结构
		jsString = jsString.slice(0, 1) + `"timestamp":${Date.now()},` + jsString.slice(1)
		core.storage.setData(md5, jsString).then(res => {
			if (requestMd5s.indexOf(md5) === -1) {
				requestMd5s.push(md5)
				core.storage.setData(core.storage.ClearKey.RequestCache, JSON.stringify(requestMd5s))
			}
			if (cache > 0) {
				// 过期10秒后自动删除
				core.delayAction(() => {
					deleteCache(md5)
				}, (cache + 10) * 1000)
			}
			cacheFix && cacheFix(md5)
		})
	}
}

/**
 * 对所有请求做去重处理
 * 对查询类请求做缓存处理
 * 一定要有的参数：
 * 	key：请求的API名称。group + fname
 * 	where：实际请求参数
 * 	necessary：请求头参数
 * 	config：缓存相关的配置参数
 * 
 * resolve：读取了缓存信息
 * reject：没有缓存信息可读取，需执行正常请求。会有一个CacheMd5的字段，记录当前存储的key，可以自行控制是否删除
 */
export const cache = (key, where, necessary, config) => {
	var {
		/**
		 * 缓存存储时间。单位秒
		 * 该接口会多少秒内的请求直接读取缓存。防止同一个接口相同的参数下短时间内多次请求
		 * 如果该值为0、false、不存在，就完全不走缓存操作，直接略过
		 * 
		 * get查询，默认值提前被设定为100
		 * 非get的其它数据库操作，默认为false，并且不能设置为其它值
		 * 云函数云对象，默认是0，可以设置为其它值
		 * -1：表示在系统启动期间一直生效
		 */
		cache,
		// 是否刷新。如果是刷新操作，将不读取缓存，但是会记录缓存
		refresh,
		/**
		 * 是否缓存固定，固定的将在启动时不会被立即清空
		 * 该值是方法，执行会回调MD5值，以供具体场景中处理
		 * 该值不能随便设置，必须在设置该值的具体场景中处理！
		 * 有设置，有读取，有清除！
		 */
		cacheFix,
		// 是否显示输出打印
		showConsole = true
	} = config || {}
	var md5fix = '-clear'
	if (cacheFix || getApp().$store.state.NoData) {
		cache = -1
		md5fix = '-cache'
	}
	// 根据完全的请求参数，获取md5值。要排除necessary的Ca-Token
	const header = {...necessary}
	delete header['Ca-Token']
	var str = key + JSON.stringify(where || {}) + JSON.stringify(header)
	const md5 = core.synctool.secret.md5(str) + md5fix
	const current = Date.now()
	// 对所有的请求做去重处理
	if (RequestActions[md5]) {
		return new Promise((resolve, reject) => {
			if (RequestActions[md5].error) {
				// 已经存在失败结果
				reject(RequestActions[md5].error)
			} else if (RequestActions[md5].res) {
				// 已经存在成功结果
				resolve(RequestActions[md5].res)
			} else {
				// 关键代码，在当前请求拿到结果后执行，并记录
				RequestActions[md5].action = ({res, error}) => {
					if (error) {
						reject(error)
						RequestActions[md5].error = error
					} else {
						resolve(res)
						RequestActions[md5].res = res
					}
				}
			}
		})
	}
	// 记录当前请求。创建当前请求对象
	RequestActions[md5] = {
		timestamp: current
	}
	/**
	 * 请求结果回调
	 * forCache：是否需要存储
	 */
	const callback = (forCache, {res, error}) => {
		if (forCache) {
			setData(res, md5, cache, cacheFix)
		}
		setRequest({res, error}, md5)
	}
	return new Promise((resolve, reject) => {
		// 没有读取到缓存的回调。forCache：是否需要缓存
		const fail = (CacheMd5) => {
			reject({
				where,
				CacheMd5,
				callback: (data) => {
					callback(!!CacheMd5, data)
				},
			})
		}
		if (cache && typeof(cache) === 'number') {
			// 优先读取本地缓存
			var res = core.storage.getDataSync(md5)
			if (res) {
				res = JSON.parse(res)
				if (!refresh && (cache === -1 || current - res.timestamp < cache * 1000)) {
					if (showConsole) {
						console.log('响应结果来自于缓存：')
						console.log(res)
					}
					res.CacheMd5 = md5 // 正常该行是不必要的，因为已经有值了
					// 在设置的有效时间内。直接返回
					callback(false, {res})
					resolve(res)
				} else {
					// 先删除这个缓存
					deleteCache(md5)
					// 再回调需要重新请求
					fail(md5)
				}
			} else {
				fail(md5)
			}
		} else {
			fail(md5)
		}
	})
}